package com.sky.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.constant.StatusConstant;
import com.sky.context.BaseContext;
import com.sky.dto.SetmealDTO;
import com.sky.dto.SetmealPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.entity.Setmeal;
import com.sky.entity.SetmealDish;
import com.sky.exception.DeletionNotAllowedException;
import com.sky.exception.SetmealEnableFailedException;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetmealDishMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.result.PageResult;
import com.sky.service.SetmealService;
import com.sky.vo.DishItemVO;
import com.sky.vo.SetmealVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * 套餐业务实现
 * @author DragonflyYue
 */
@Service
@Slf4j
public class SetmealServiceImpl implements SetmealService {

    @Autowired
    private SetmealMapper setmealMapper;

    @Autowired
    private SetmealDishMapper setmealDishMapper;

    @Autowired
    private DishMapper dishMapper;


    /**
     * 新增套餐, 同时需要保存套餐和菜品的关联关系
     * @param setmealDTO
     */
    @Transactional        //事务管理,涉及多个表的操作,要保证数据的一致性
    @Override
    public void saveWithDish(SetmealDTO setmealDTO) {
        //将数据和实体类关联
        Setmeal setmeal = new Setmeal();
        //数据拷贝
        BeanUtils.copyProperties(setmealDTO,setmeal);
            //把setmealDTO这里面的值拷贝到setmeal

        //为啥上面要单独创建空的实体类,,而不是直接传入setmealDTO,作为insert的参数??
        //1.在分层架构中（如MVC），DTO通常用于在层之间传递数据。直接传递DTO到数据访问层（DAO或Mapper层）,
            // 可能会破坏层之间的解耦。通过将DTO转换为实体类，你可以确保数据访问层只与实体类交互，
            // 这有助于保持代码的清晰和可维护性。
        //2. 因为你setmealDTO里面还包括了SetmealDish这个套餐菜品关系这个属性,
            // 你直接传,两个表setmeal和SetmealDish没法同时赋值,,,所以才要把实体类拆开,,也就是拆出对应的表


        //向套餐表插入数据
        setmealMapper.insert(setmeal);

        //下面的关系表,,要先获取到里面的数据,然后才能插入到setmealDish这个关系表中

        //1.获取到对应的setmealId,
            // 因为SetmealDish对象需要知道它们属于哪个套餐,才能建立关系
            //如果自己根据自己的想法,不获取id,把获取到的setmealDishes直接插入到  setmealDish这个关系表中
            // 这样做会导致插入的SetmealDish记录缺少必要的套餐ID信息，从而无法正确建立套餐和菜品之间的关系。
        Long setmealId = setmeal.getId();
        //2.通过get方法,获取对象属性setmealDishes
        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
        //3. 把获取到的setmealId设置到setmealDishes里面
        setmealDishes.forEach(setmealDish -> {
            setmealDish.setSetmealId(setmealId);
        });
        //上面这里用增强for也可以实现,,这里用的是foreach+Lamda表达式,就是比较简洁
        //这里主要是要理解这段代码的作用,,,这里是为了通过setmealId将setmeal和setmealDish表关联起来
            //怎么关联的??  就是setmealDish套餐菜品关联表中,, 根据setmealId插入对应的关联信息呀

        //4. 向套餐菜品关系表中 插入关联数据
            //这个方法就是批量插入,,还真的是批量插入,里面还用到了foreach遍历的标签,,
            //自己还以为和上面套餐表一样,只是简单的插入一个语句呢,原来是放到sql语句中去遍历,
            //这也是为啥最开始我会想,怎么遍历的把一个套餐中,插入对应的菜品数据呢,因为一个套餐会有多个菜品
        setmealDishMapper.insertBatch(setmealDishes);

    }

    /**
     * 套餐分页查询
     * @param setmealPageQueryDTO
     * @return
     */
    @Override
    public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {

        //涉及分页逻辑,可以通过工具类自动完成分页数据的计算
        PageHelper.startPage(setmealPageQueryDTO.getPage(),setmealPageQueryDTO.getPageSize());
        Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);

        return new PageResult(page.getTotal(),page.getResult());
    }

    /**
     * 批量删除套餐
     * 传入的参数是套餐id
     * @param ids
     */
    @Transactional
    @Override
    public void deleteBatch(List<Long> ids) {
        //这里给的答案中,,遍历没有放到.xml中使用foreach;而是直接在service层处理的
        ids.forEach(id->{
            //通过套餐id获取对应的setmeal对象,即查询到对应的套餐信息
            Setmeal setmeal = setmealMapper.getById(id);
            if (setmeal.getStatus() == StatusConstant.ENABLE){
                //起售中的菜品不能删除,,所以这里单独处理的异常
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        });

        //在前面删除dish表中的菜品的时候,还要额外考虑,套餐中是否有关联,有关联不能删除
            //而当前直接删除套餐,就不用考虑了
        //优化:   下面直接在这里遍历的调用的delete语句,其实也可以放到.xml中使用foreach遍历的删除
        //还有下面的代码完全可以放到上面的foreach遍历中,完全一样,
            // 可能只是为了逻辑清晰,上面的是查询逻辑,下面的是删除逻辑对应的代码

        ids.forEach(setmealId->{
            //这里相当于从ids这个集合中取出一个, 变量名为setmealId;  这都是套餐id,不是菜品id

            //删除套餐表中的数据
            setmealMapper.deleteById(setmealId);

            //删除套餐菜品关系表中的数据
            setmealDishMapper.deleteBySetmealId(setmealId);
                //这里还要删除套餐关系表中的数据,,我刚才还想,除了套餐表中的也没啥关联的了
                //在前面dish菜品表批量删除的时候,要考虑的是口味表,对应关联的数据也要删除

        });
        //DELETE FROM employee WHERE user_name = '老妖怪'
    }

    /**
     * 根据套餐id查询套餐,,包括关联的菜品数据,,用于页面回显数据
     * @param id
     * @return
     */
    @Override
    public SetmealVO getByIdWithDish(Long id) {

        //查询套餐表的套餐信息
        Setmeal setmeal = setmealMapper.getById(id);

        //查询套餐关系表中关联的信息
            //一个套餐可能关联多个信息,所以用的List集合
            //而且你看setmealVO这个类中,setmealDishes这个属性本身就定义的List
        List<SetmealDish> setmealDishes = setmealDishMapper.getBySetmealId(id);

        SetmealVO setmealVO = new SetmealVO();

        BeanUtils.copyProperties(setmeal,setmealVO);

        //直接调用setmealVO对象中的set方法,为SetmealDishes这个属性赋值
        setmealVO.setSetmealDishes(setmealDishes);

        return setmealVO;
    }

    /**
     * 修改套餐
     * @param setmealDTO
     */
    @Transactional
    @Override
    public void update(SetmealDTO setmealDTO) {

        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO,setmeal);
        //修改套餐--原来下面实现过了,,就不用自己再实现动态update的sql语句了
        setmealMapper.update(setmeal);

        //修改套餐菜品关联关系
            //先删除对应的关联关系,,最后再添加, ------自己真想不到

        //删除对应的关联关系
            //为啥要先取出setmealId,因为下面deleteBySetmealId这个方法要求传入的类型是Long型的setmealId
            //因为这个删除方法,以前就定义过,所以这里为了可以直接用;要不你还得重新写sql语句
        Long setmealId = setmealDTO.getId();
        setmealDishMapper.deleteBySetmealId(setmealId);

        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
        //因为要把两个表(setmeal和setmeal_dish表)关联起来,需要通过设置对应的setmealId,进行关联
            //自己这一步总忘,总想下面直接批量插入,,想不起来还要设置setmealId进行关联
        setmealDishes.forEach(setmealDish -> {
            setmealDish.setSetmealId(setmealId);
        });
        //上面关联好,这里就可以进行批量插入了
        setmealDishMapper.insertBatch(setmealDishes);

    }

    /**
     * 套餐起售停售
     * 没做出来,,最终看的答案
     * @param status
     * @param id
     */
    @Transactional
    @Override
    public void startOrStop(Integer status, Long id) {

        //如果套餐中,有停售的菜品,是不能起售的---可以弄个异常提示信息
        //起售套餐时，判断套餐内是否有停售菜品，有停售菜品提示"套餐内包含未启售菜品，无法启售"
        if (status == StatusConstant.ENABLE){
            //关键是这里要查询两个表的结果,自己就想不到
                //查dish表和setmeal_dish表,就能把有关联关系的,且把对应的菜品信息都查出来
            //select a.* from dish a left join setmeal_dish b on a.id = b.dish_id where b.setmeal_id = ?
            List<Dish> dishList = dishMapper.getBySetmealId(id);
                //上面这种过个表之间的关系,查询,自己想不到,总是感觉陌生

            if (dishList != null && dishList.size() > 0){

                dishList.forEach(dish ->{
                    //如果套餐中有停售的菜品,,这里就抛出异常
                    if (dish.getStatus() == StatusConstant.DISABLE){
                        throw new SetmealEnableFailedException(MessageConstant.SETMEAL_ENABLE_FAILED);
                    }

                });
            }
        }

        //setmeal表进行修改
            //下面的update方法要求传入的是Setmeal类型,我还以为要先用套餐id查询对应的setmeal才行,
                //结果下面可以直接用构造器,,,其实属性少,也可以直接用set方法,放入到setmeal对象中,就可以了
        Setmeal setmeal = Setmeal.builder()
                .id(id)
                .status(status)
                .build();
        setmealMapper.update(setmeal);
    }

    /**
     * 条件查询
     * 这是个动态sql查询,根据传进来的条件,,来查询对应的套餐数据
     * @param setmeal
     * @return
     */
    public List<Setmeal> list(Setmeal setmeal) {
        List<Setmeal> list = setmealMapper.list(setmeal);
        return list;
    }

    /**
     * 根据id查询菜品选项
     * @param id
     * @return
     */
    public List<DishItemVO> getDishItemById(Long id) {
        return setmealMapper.getDishItemBySetmealId(id);
    }
}
